Appearance
[中文版本](/posts/c教程/zh-CN/07-贴近系统/069-Error Handling and Return Codes)
Every C program, from the tiniest script to the Linux kernel itself, relies on error codes and return values to communicate success or failure. Unlike higher-level languages, C gives you no exceptions, only clear, explicit status codes anderrno.
Mastering these patterns will make your programs robust, predictable, and professional.
Step 1. Exit Codes and main()
Every process returns an integer exit code to the operating system. Conventionally:
0→ success- nonzero → failure or specific error
#include <stdio.h>
int main(void) {
printf("Everything OK!\n");
return 0; // exit success
}Check it in your shell:
./program
echo $?Output:
Everything OK!
0If you return a nonzero value:
return 1;then$? will be1, meaning failure.
Step 2. Using EXIT_SUCCESS and EXIT_FAILURE
Instead of hardcoded numbers, use standard macros from<stdlib.h>:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("Failed to open file.\n");
return EXIT_FAILURE;
}These improve portability and readability.
Step 3. The Global errno
When a library or system call fails, it usually sets a global variable namederrno. It’s declared in<errno.h>.
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd = open("nonexistent.txt", O_RDONLY);
if (fd == -1) {
printf("Error opening file: %s\n", strerror(errno));
}
}Output:
Error opening file: No such file or directoryerrno stores an integer code, butstrerror() converts it into a readable message.
Step 4. Common errno Values
| Code | Macro | Meaning |
|---|---|---|
| 2 | ENOENT | No such file or directory |
| 13 | EACCES | Permission denied |
| 12 | ENOMEM | Out of memory |
| 22 | EINVAL | Invalid argument |
| 9 | EBADF | Bad file descriptor |
| 11 | EAGAIN | Resource temporarily unavailable |
You can check them directly:
if (errno == EACCES) { ... }Step 5. The perror() Function
A simpler way to print error messages isperror(), it automatically uses the currenterrno.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd = open("nothing.txt", O_RDONLY);
if (fd == -1) {
perror("open");
}
}Output:
open: No such file or directoryStep 6. Returning Meaningful Codes
Good C programs translate internal errors into meaningful exit codes.
Example: file copy program
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s src dest\n", argv[0]);
return 1;
}
FILE *src = fopen(argv[1], "r");
if (!src) {
perror("fopen src");
return 2;
}
FILE *dst = fopen(argv[2], "w");
if (!dst) {
perror("fopen dst");
fclose(src);
return 3;
}
fclose(src);
fclose(dst);
return 0;
}Now each exit code represents a specific type of failure.
Step 7. Resetting and Checking errno
Some system calls seterrno only when they fail. So always reset it before use if you plan to inspect it later:
#include <errno.h>
errno = 0;
if (some_syscall() == -1) {
perror("syscall failed");
}Step 8. Custom Error Handling Helpers
You can create your own function to simplify error handling.
#include <stdio.h>
#include <stdlib.h>
void die(const char *msg) {
perror(msg);
exit(EXIT_FAILURE);
}
int main(void) {
FILE *f = fopen("no.txt", "r");
if (!f) die("fopen");
}Output:
fopen: No such file or directoryThis pattern appears throughout Unix utilities.
Step 9. Handling Partial Failures
Not all errors should abort your program. Sometimes you should log, retry, or ignore.
FILE *f = fopen("optional.conf", "r");
if (!f) {
fprintf(stderr, "Warning: config file missing, using defaults.\n");
}This kind of graceful degradation is good design.
Step 10. Tiny Code: Safe File Reader
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
FILE *f = fopen("data.txt", "r");
if (!f) {
fprintf(stderr, "Error: %s\n", strerror(errno));
return EXIT_FAILURE;
}
char buf[64];
while (fgets(buf, sizeof(buf), f))
printf("%s", buf);
fclose(f);
return EXIT_SUCCESS;
}Output (if missing file):
Error: No such file or directoryWhy It Matters
Error handling separates toy programs from real software:
- Every syscall can fail, be prepared.
- Always check return values.
- Always report why it failed.
By convention:
- Return
0on success. - Return nonzero for recoverable or fatal errors.
- Print messages to
stderr, notstdout.
Try It Yourself
- Open a nonexistent file and print the full
errnovalue. - Build a small utility that returns specific codes for specific problems.
- Use
perror()vsstrerror()and compare their outputs. - Add a
die()helper to your previous exercises. - Write a safe wrapper that retries system calls when
errno == EAGAIN.
Next, you’ll put all of this together in Practice 70: Building a Mini Shell in C, where you’ll handle processes, pipes, and signals to create your own working Unix shell prototype.
